package ss.pku.jsa.type.inference;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;

import ss.pku.jsa.type.constraint.solver.QError;


@SuppressWarnings({ "rawtypes", "unchecked" })
public class FFIResults {
	static final int RETURNQ = 0;
	static final int PARAMQ = 1;
	static final String PFIELD = "pointerfield*";
	BufferedReader in = null;
	List<FuncSig> funcs = new Vector();

	public FFIResults(String filename) {
		try {
			this.in = new BufferedReader(new FileReader(filename));
		} catch (FileNotFoundException localFileNotFoundException) {
			throw new IllegalArgumentException("Could not open " + filename);
		}
		String line = null;
		try {
			while ((line = this.in.readLine()) != null)
				this.funcs.add(new FuncSig(line));
		} catch (IOException e) {
			throw new IllegalArgumentException(e.toString());
		}
	}

	class FuncSig {
		FFIResults.JNIMethodName name;
		List<List<String>> quals = new Vector();

		FuncSig(String s) {
			int pcmLen = s.indexOf('(');

			if (pcmLen < 1) {
				throw new IllegalArgumentException(
						"Doesn't seem to be any qual info for " + s);
			}
			String s1 = s.substring(0, pcmLen);
			String s2 = s.substring(pcmLen);

			boolean overloaded = s1.indexOf("__") > -1;
			if (overloaded)
				this.name = new FFIResults.OverloadedJNIMethodName(s1);
			else {
				this.name = new FFIResults.JNIMethodName(s1);
			}
			MethodQType qt = (MethodQType) JqualPlugin.analysis.nativeMethods
					.get(this.name);

			if (qt == null) {
				JqualPlugin.printlnLog("ffiLog", "unmatched: " + this.name);
				return;
			}

			List qualsSig = parseSig(s2);

			if (qualsSig.size() - 3 != qt.getParameterQTypes().size()) {
				JqualPlugin.printlnLog("ffiLog", "mismatched params: "
						+ this.name);
				return;
			}
			applyQuals(qt.getReturnQType(), (String) qualsSig.get(0), 0);

			int pos = 3;

			for (QType pqt : qt.getParameterQTypes()) {
				applyQuals(pqt.deref(), (String) qualsSig.get(pos), 1);
				pos++;
			}
		}

		List<String> parseSig(String sigString) {
			List ret = new ArrayList();
			int pos = sigString.indexOf(')');
			while (pos != -1) {
				ret.add(sigString.substring(0, pos + 1));
				sigString = sigString.substring(pos + 1);
				pos = sigString.indexOf(')');
			}
			return ret;
		}

		void applyQuals(QType qt, String s, int retOrParam) {
			if (qt == null) {
				return;
			}
			if ((s.charAt(0) != '(') || (s.charAt(s.length() - 1) != ')')) {
				throw new IllegalArgumentException(s
						+ " is not a valid quals string");
			}
			if (s.length() > 2) {
				JqualPlugin.analysis.ffiNumAnnotations += 1;
			}

			s = s.substring(1, s.length() - 1);
			String[] quals = s.split(" ");

			for (String qual : quals) {
				qual = qual.trim();
				if ((!JqualPlugin.ffiInferEnum)
						&& (qual.toLowerCase().contains("enum"))) {
					continue;
				}
				if (qual.length() > 0) {
					if (qual.indexOf("pointerfield*") == 0) {
						if (!(qt instanceof ObjectQType)) {
							JqualPlugin.printlnLog("warningLog", qt.name
									+ " is not an ObjectQType for " + qual);
							System.err.println(qt.name
									+ " is not an ObjectQType for " + qual);
							return;
						}
						ObjectQType handle = (ObjectQType) qt;

						if (handle.gtkPointerField != null) {
							qt = handle.gtkPointerField;
						} else {
							Location loc = new Location("FFI_annots", 0L, 0L);

							TypeKey intType = TypeKey.getTypeKey("I", 0, "int");
							PrimitiveQType fqt32 = new PrimitiveQType(intType,
									loc, "pointer", false);
							TypeKey declaringType = TypeKey.getTypeKey(
									"Lorg/gnu/glib/Handle32Bits;", 0,
									"Handle32Bits");
							RefQType rqt32 = new RefQType(loc, false, fqt32,
									false);
							FieldKey fkey32 = FieldKey.getFieldKey(
									declaringType, "pointer", intType);
							rqt32.setKey(fkey32);

							handle.addFieldQType(fkey32, rqt32);

							TypeKey longType = TypeKey.getTypeKey("J", 0,
									"long");
							PrimitiveQType fqt64 = new PrimitiveQType(longType,
									loc, "pointer", false);
							declaringType = TypeKey.getTypeKey(
									"Lorg/gnu/glib/Handle64Bits;", 0,
									"Handle64Bits");
							RefQType rqt64 = new RefQType(loc, false, fqt64,
									false);
							FieldKey fkey64 = FieldKey.getFieldKey(
									declaringType, "pointer", longType);

							rqt64.setKey(fkey64);
							handle.addFieldQType(fkey64, rqt64);
							CQual.makeEdge(Constraint.Kind.EQ, rqt32.deref()
									.getQual(), rqt64.deref().getQual(),
									QualGate.EMPTY, qt.l, 0,
									QError.FFI_SPECIAL_HANDLE);
							qt = fqt64;
						}

						qual = qual.substring("pointerfield*".length());
					}

					Qualifier q = Qualifier.findQualifierConstant(qual);
					if ((q == null) && (qual.contains("ptr"))) {
						System.err.println("using generic ffi_pointer");
						q = Qualifier.findQualifierConstant("ffi_pointer");
					}

					if (retOrParam == 0)
						CQual.makeEdge(Constraint.Kind.EQ, q, qt.deref()
								.getQual(), QualGate.EMPTY, qt.l, 0,
								QError.FFI_INFERRED);
					else
						CQual.makeEdge(Constraint.Kind.EQ,
								qt.deref().getQual(), q, QualGate.EMPTY, qt.l,
								0, QError.FFI_INFERRED);
				}
			}
		}
	}

	public static class JNIMethodName {
		String name;

		public JNIMethodName(IMethodBinding m) {
			StringBuilder ret = new StringBuilder("Java_");
			String qname = jniEscape(m.getDeclaringClass().getQualifiedName());
			ret.append(qname);
			ret.append("_");
			ret.append(jniEscape(m.getName()));
			this.name = ret.toString();
		}

		public JNIMethodName(String name) {
			this.name = name;
		}

		public static String jniEscape(String s) {
			String ret = s.replace("_", "_1");
			ret = ret.replace(";", "_2");
			ret = ret.replace("[", "_3");
			ret = ret.replace(".", "_");
			return ret;
		}

		public boolean equals(Object o) {
			if (!(o instanceof JNIMethodName)) {
				return false;
			}
			return ((JNIMethodName) o).name.equals(this.name);
		}

		public int hashCode() {
			return this.name.hashCode();
		}

		public String toString() {
			return this.name;
		}
	}

	public static class OverloadedJNIMethodName extends
			FFIResults.JNIMethodName {
		public OverloadedJNIMethodName(IMethodBinding m) {
			super("");
			this.name += "__";
			for (ITypeBinding p : m.getParameterTypes())
				this.name += getJNITypeString(p);
		}

		public OverloadedJNIMethodName(String name) {
			super("");
		}

		public static String getJNITypeString(ITypeBinding t) {
			int d = t.getDimensions();
			String arr = "";
			while (d-- > 0) {
				arr = arr + "_3";
			}
			if (t.isArray())
				t = t.getElementType();
			String ret = "";
			if (t.isPrimitive()) {
				String name = t.getName();
				if (name.equals("boolean")) {
					ret = "Z";
				} else {
					if (name.equals("byte")) {
						ret = "B";
					} else {
						if (name.equals("char")) {
							ret = "C";
						} else {
							if (name.equals("short")) {
								ret = "S";
							} else {
								if (name.equals("int")) {
								} else {
									if (name.equals("long")) {
										ret = "J";
									} else {
										if (name.equals("float")) {
											ret = "F";
										} else {
											if (name.equals("double"))
												ret = "D";
											else
												throw new IllegalArgumentException(
														"Don't have translation for type "
																+ name);
										}
									}
								}
							}
						}
					}
				}
			} else {
				String qname = t.getQualifiedName();
				ret = jniEscape(new StringBuilder("L").append(qname).toString())
						+ "_2";
			}
			return arr + ret;
		}

		public boolean equals(Object o) {
			if (!(o instanceof OverloadedJNIMethodName)) {
				return false;
			}
			return ((OverloadedJNIMethodName) o).name.equals(this.name);
		}
	}
}
